#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <dlfcn.h>
#include <getopt.h>
#include <string.h>

#include "capture.h"


GLOBALS global;

#define INPUT_MODULE "libinput_yuyv.so"
#define OUTPUT_MODULE "liboutput_file.so"

#define DEFAULT_INPUT_PARAM "libinput_yuyv.so --resolution 640x480 --device /dev/video0"
#define DEFAULT_OUTPUT_PARAM "liboutput_file.so "

static char *inputParam = NULL;
static char *outputParam[MAX_OUTPUT_PLUGINS];
  
static char *inputName = NULL;
static char *outputName[MAX_OUTPUT_PLUGINS] = {NULL,};

static int run_time = 3;

static void help(char *progname)
{
    fprintf(stderr, "-----------------------------------------------------------------------\n");
    fprintf(stderr, "Usage: %s\n" \
            "  -i | --input \"<input-plugin.so> [parameters]\"\n" \
            "  -o | --output \"<output-plugin.so> [parameters]\"\n" \
            " [-a | --avi]........: enable avi mode\n" \
            " [-h | --help ]........: display this help\n" \
            " [-t | --time ]........: the running time \n" \
            " [-v | --version ].....: display version information\n", progname);
    fprintf(stderr, "-----------------------------------------------------------------------\n");
    fprintf(stderr, "Example #1:\n" \
            " To open an YUYV camera \"/dev/video1\" and output it on file:\n" \
            "  %s -i \"-n input_uvc.so -d /dev/video1\" -o \"-n output_file.so\"\n", progname);
    fprintf(stderr, "-----------------------------------------------------------------------\n");
    /*
      fprintf(stderr, "Example #2:\n" \
      " To open an UVC webcam and stream via HTTP port 8090:\n" \
      "  %s -i \"input_uvc.so\" -o \"output_http.so -p 8090\"\n", progname);
      fprintf(stderr, "-----------------------------------------------------------------------\n");
      fprintf(stderr, "Example #3:\n" \
      " To get help for a certain input plugin:\n" \
      "  %s -i \"input_uvc.so --help\"\n", progname);
      fprintf(stderr, "-----------------------------------------------------------------------\n");
  
      fprintf(stderr, "In case the modules (=plugins) can not be found:\n" \
      " * Set the default search path for the modules with:\n" \
      "   export LD_LIBRARY_PATH=/path/to/plugins,\n" \
      " * or put the plugins into the \"/lib/\" or \"/usr/lib\" folder,\n" \
      " * or instead of just providing the plugin file name, use a complete\n" \
      "   path and filename:\n" \
      "   %s -i \"/path/to/modules/input_uvc.so\"\n", progname);
    */
  
    fprintf(stderr, "-----------------------------------------------------------------------\n");
}


static void InitGlobal()
{
    global.stopFlag  = 0;
    global.buf       = NULL;
    global.bufSize   = 0;
    global.in.plugin = NULL;
    global.avi_mode = 0;
    
    /* this mutex and the conditional variable are used to synchronize access to the global picture buffer */
    if( pthread_mutex_init(&global.db, NULL) != 0 ) {
        exit(EXIT_FAILURE);
    }
    if( pthread_cond_init(&global.db_update, NULL) != 0 ) {
        exit(EXIT_FAILURE);
    }
}

static void InitInputModule(char *moduleName)
{
    fprintf(stderr, "init input plugin %s starting.........\n", moduleName);
    global.in.plugin = moduleName;	//don't need to malloc, in main function had done it.
    global.in.handle = dlopen(global.in.plugin, RTLD_LAZY);
    if ( !global.in.handle )
    {
        fprintf (stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    global.in.init = dlsym(global.in.handle, "input_init");
    if ( global.in.init == NULL ) {
        fprintf(stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    global.in.stop = dlsym(global.in.handle, "input_stop");
    if ( global.in.stop == NULL ) {
        fprintf(stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    global.in.run = dlsym(global.in.handle, "input_run");
    if ( global.in.run == NULL ) {
        fprintf(stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    fprintf(stderr, "init input plugin %s completed..........\n", moduleName);
}

static void InitOutputModule(unsigned char index, char *moduleName)
{
    fprintf(stderr, "init output plugin %s starting.........\n", moduleName);
    global.out[index].plugin = moduleName;	//don't need to malloc, in main function had done it.
    global.out[index].handle = dlopen(global.out[index].plugin, RTLD_LAZY);
    if ( !global.out[index].handle )
    {
        fprintf (stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    global.out[index].init = dlsym(global.out[index].handle, "output_init");
    if ( global.out[index].init == NULL ) {
        fprintf(stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    global.out[index].stop = dlsym(global.out[index].handle, "output_stop");
    if ( global.out[index].stop == NULL ) {
        fprintf(stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    global.out[index].run = dlsym(global.out[index].handle, "output_run");
    if ( global.out[index].run == NULL ) {
        fprintf(stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    fprintf(stderr, "init output plugin %s completed..........\n", moduleName);
}

void signal_handler(int sig)
{
    global.stopFlag = 1;
    int i;
    //首先关闭 使用者， 而不是生产者。 防止没有了供应依然访问。
    for (i = 0; i < global.outcnt; i++) 
    {
        global.out[i].stop();
    }
    global.in.stop();
    fprintf(stderr, "you press C-c.  the application will exit.\n");
    exit(EXIT_SUCCESS);
}

static void ParseCmdLine(int argc, char *argv[])
{
    //如果没有给定输入输出参数，就使用这两个默认的参数
    inputParam  = DEFAULT_INPUT_PARAM ;
    outputParam[0] = DEFAULT_OUTPUT_PARAM ;
    
    global.outcnt = 0;		//马上会使用到，所以提在这儿初始化
    int inited_inputParam = 0;
    while(1) 
    {
        int option_index = 0, c = 0;
        static struct option long_options[] = 
            {
                {"h", no_argument, 0, 0},
                {"help", no_argument, 0, 0},
                {"i", required_argument, 0, 0},
                {"input", required_argument, 0, 0},
                {"o", required_argument, 0, 0},
                {"output", required_argument, 0, 0},
                {"v", no_argument, 0, 0},
                {"version", no_argument, 0, 0},
		{"a", no_argument, 0, 0},
                {"avi", no_argument, 0, 0},
		{"t", required_argument, 0, 0},
                {"time", required_argument, 0, 0},
                {0, 0, 0, 0}
            };

        c = getopt_long_only(argc, argv, "", long_options, &option_index);

        /* no more options to parse */
        if(c == -1) break;

        /* unrecognized option */
        if(c == '?') {
            help(argv[0]);
            exit(EXIT_FAILURE);
        }

        switch (option_index) 
        {
            /* h, help */
	    case 0:
	    case 1:
            help(argv[0]);
            exit(EXIT_SUCCESS);
            break;

            /* i, input */
	    case 2:
	    case 3:
            if(inited_inputParam )
            {
                fprintf(stderr, "support only one input plugin!\n");
                exit(EXIT_FAILURE);
            }
            else
            {
                inputParam = strdup(optarg);
                inited_inputParam = 1;
            }
            break;

            /* o, output */
	    case 4:
	    case 5:
            outputParam[global.outcnt++] = strdup(optarg);
            break;

            /* v, version */
	    case 6:
	    case 7:
            printf("video capture Version: %s\n" \
                   "Compilation Date.....: %s\n" \
                   "Compilation Time.....: %s\n", SOURCE_VERSION, __DATE__, __TIME__);
            exit(EXIT_SUCCESS);
            break;
	    
	    //output avi mode
	    case 8:
	    case 9:
	      global.avi_mode = 1;
	      break;
	      
	    case 10:
	    case 11:
	      run_time = atoi(optarg);
	      if(run_time <= 0)
	      {
		fprintf(stderr, "the run_time(%d) which your input is not correct, use default run_time 3 second.\n ", run_time);
		run_time = 3;		
	      }
	      break;
	    default:
            help(argv[0]);
            exit(EXIT_FAILURE);
        }
    }
    /* check if at least one output plugin was selected */
    if ( global.outcnt == 0 ) {
        /* no? Then use the default plugin instead */
        global.outcnt = 1;
    } 
}

int main(int argc, char *argv[])
{
  /* initialise the global variables */
    InitGlobal();
    
    ParseCmdLine(argc, argv);
    
    size_t tmp = 0;
    int i;
    
    /* get input plugin name*/
    tmp = (size_t)(strchr(inputParam, ' ')-inputParam);
    inputName = (tmp > 0)?strndup(inputParam, tmp):strdup(inputParam);
    
    /* get output plugin names*/
    for (i = 0; i<global.outcnt; i++) 
    {
        tmp = (size_t)(strchr(outputParam[i], ' ')-outputParam[i]);
        outputName[i] = (tmp > 0)?strndup(outputParam[i], tmp):strdup(outputParam[i]);
        //fprintf(stderr, "output device is %s", outputName[i]);
    }
      
    
    InitInputModule(inputName);
    for (i = 0; i<global.outcnt; i++) 
    {
        InitOutputModule( i, outputName[i]);
    }
    
    //移动到各组件初始化后， 防止在未初始化时使用， 以导致段错误（非法使用pthread_join 因为线程还未创建）。
    /* register signal handler for <CTRL>+C in order to clean up 
    if (signal(SIGINT, signal_handler) == SIG_ERR) {
        exit(EXIT_FAILURE);
    }
        */
    
    //---------------------------------------
    global.in.init(inputParam, &global);
    global.in.run();
    //FILE *fp = fopen("/home/cf/my.jpeg", "wb");
    //fwrite(global.buf, global.bufSize, 1, fp );
    
    for (i = 0; i < global.outcnt; i++) 
    {
        global.out[i].init(outputParam[i], &global);
        global.out[i].run();
    }

    /* register signal handler for <CTRL>+C in order to clean up */
    if (signal(SIGINT, signal_handler) == SIG_ERR) {
        exit(EXIT_FAILURE);
    }
    
    //用以测试使用， 延时观察
    sleep(run_time);
    
    global.stopFlag = 1;
    
    //首先关闭 使用者， 而不是生产者。 防止没有了供应依然访问。
    for (i = 0; i < global.outcnt; i++) 
    {
        global.out[i].stop();
    }
    global.in.stop();
    
    
    return 0;
}
